#define ZCORE_SOURCE
#include "Log.hpp"
#include "CmdParser.hpp"
#include "Tools.hpp"
#ifdef ZZZ_LIB_BOOST
  #include <boost/date_time/posix_time/posix_time.hpp>
#else
  #include <ctime>
#endif
#include "FileTools.hpp"
#include "StringPrintf.hpp"
#include <EnvDetect.hpp>
#include <3rdParty/StackWalker.h>
#ifdef ZZZ_OS_WIN
#include <windows.h>
#endif

namespace zzz{
int VerboseLevel::old_=ZINFO;
int VerboseLevel::verbose_level_=ZINFO;

// If VERBOSE_xxx is defined, a certain verbose level is used
// else default for DEBUG is ZDEBUG and for RELEASE is ZVERBOSE
#if defined VERBOSE_DEBUG
  const int DEFAULT_VERBOSE = ZDEBUG;
#elif defined VERBOSE_VERBOSE
  const int DEFAULT_VERBOSE = ZVERBOSE;
#elif defined VERBOSE_INFO
  const int DEFAULT_VERBOSE = ZINFO;
#elif defined VERBOSE_WARNING
  const int DEFAULT_VERBOSE = ZWARNING;
#elif defined VERBOSE_ERROR
  const int DEFAULT_VERBOSE = ZERROR;
#elif defined VERBOSE_FATAL
  const int DEFAULT_VERBOSE = ZFATAL;
#elif defined ZZZ_DEBUG
  const int DEFAULT_VERBOSE = ZDEBUG;
#else
  const int DEFAULT_VERBOSE = ZVERBOSE;
#endif

ZFLAGS_INT(verbose_level, DEFAULT_VERBOSE, "Set Verbose Level");
ZFLAGS_SWITCH(verbose_debug, "Set Verbose Level: Debug (Show all msg)");
ZFLAGS_SWITCH(verbose_verbose, "Set Verbose Level: Verbose (Show additional msg)");
ZFLAGS_SWITCH(verbose_info, "Set Verbose Level: Info (Show normal msg)");
ZFLAGS_SWITCH(verbose_more, "Set Verbose Level: More (Show more important and error msg)");
ZFLAGS_SWITCH(verbose_error, "Set Verbose Level: Error (Only show error msg)");
ZFLAGS_SWITCH(verbose_fatal, "Set Verbose Level: Fatal (Only show fatal msg)");
ZFLAGS_STRING(glog_file, "", "Global Logger File");
ZFLAGS_BOOL(auto_log, true, "Automatically log to file");
ZFLAGS_STRING(auto_log_dir, GetEnv("ZLOG_DIR"), "Automatically log file directory");

void PostParseVerbose() {
  if (ZFLAG_verbose_debug)
    ZFLAG_verbose_level=ZDEBUG;
  else if (ZFLAG_verbose_verbose)
    ZFLAG_verbose_level=ZVERBOSE;
  else if (ZFLAG_verbose_info)
    ZFLAG_verbose_level=ZINFO;
  else if (ZFLAG_verbose_more)
    ZFLAG_verbose_level=ZMORE;
  else if (ZFLAG_verbose_error)
    ZFLAG_verbose_level=ZERROR;
  VerboseLevel::Set(ZFLAG_verbose_level);
}
ZFLAGS_POST(PostParseVerbose);

GlobalLogger::GlobalLogger()
    : cur_color_(RED),
      next_color_(WHITE) {
  ReallySetColor(); // set color to white
  ostreams_.push_back(&cout);
  if (!ZFLAG_glog_file.empty()) {
    ofstream *fo = new ofstream(ZFLAG_glog_file.c_str());
    // zlog is not available yet! so we cannot use ZCHECK
    if (!fo->good()) {
      cerr<<"Cannot open file "<<ZFLAG_glog_file<<endl;
    }
    ofstreams_.push_back(fo);
    ostreams_.push_back(fo);
  }

  if (ZFLAG_auto_log && !ZFLAG_auto_log_dir.empty()) {
    ZFLAG_auto_log_dir = CompleteDirName(ZFLAG_auto_log_dir);
#ifdef ZZZ_LIB_BOOST
    std::ostringstream msg;
    const boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
    boost::posix_time::time_facet *lf = new boost::posix_time::time_facet("%Y_%m_%d-%H_%M_%S");
    msg.imbue(std::locale(msg.getloc(),lf));
    msg << now;
    const string timestr = msg.str();
#else
    time_t rawtime;
    time(&rawtime);
    const string timestr = ctime(&rawtime);
#endif
    const string progstr = GetFilename(*ZGLOBAL_GET(string*, "zCurrentProgramName"));
    const string dirstr = CompleteDirName(PathFile(ZFLAG_auto_log_dir, progstr));
    if (!DirExists(dirstr) && !MakeDir(dirstr)) {
      cerr<<"Cannot create directory "<<dirstr<<endl;
      cerr<<"This directory is to store default log files for GlobalLogger. If you do not need the log file, set --no_auto_log\n";
      exit(-1);
    }
#if defined(ZZZ_DEBUG)
    const string filename = PathFile(dirstr, StringPrintf("[%s][%s][DEBUG].zlog", timestr.c_str(), progstr.c_str()));
#elif defined(ZZZ_FASTEST)
    const string filename = PathFile(dirstr, StringPrintf("[%s][%s][FASTEST].zlog", timestr.c_str(), progstr.c_str()));
#else
    const string filename = PathFile(dirstr, StringPrintf("[%s][%s][RELEASE].zlog", timestr.c_str(), progstr.c_str()));
#endif
    ofstream *fo = new ofstream(filename);
    // zlog is not available yet! so we cannot use ZCHECK
    if (!fo->good()) {
      cerr<<"Cannot open file "<<filename<<endl;
      cerr<<"This file is a default log file for GlobalLogger. If you do not need the log file, set --no_auto_log\n";
      exit(-1);
    }
    ofstreams_.push_back(fo);
    ostreams_.push_back(fo);
  }
}

GlobalLogger::~GlobalLogger() {
  SetColor(GRAY);
  ReallySetColor();
}

void GlobalLogger::Clear() {
  ostreams_.clear();
  for (zuint i=0; i<ofstreams_.size(); i++)
    delete ofstreams_[i];
  ofstreams_.clear();
}

void GlobalLogger::Add(ostream *s) {
  ostreams_.push_back(s);
}

void GlobalLogger::SetColor(LoggerColor color) {
  next_color_ = color;
}

#ifdef ZZZ_OS_WIN
void GlobalLogger::ReallySetColor() {
  if (cur_color_ == next_color_) return;
  static HANDLE hCon=NULL;
  if(hCon == NULL) {
    hCon = GetStdHandle(STD_OUTPUT_HANDLE);
  }
  switch(next_color_) {
  case WHITE:    // White on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
    break;
  case RED:    // Red on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_INTENSITY | FOREGROUND_RED);
    break;
  case GREEN:    // Green on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_INTENSITY | FOREGROUND_GREEN);
    break;
  case BLUE:    // Blue on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_INTENSITY | FOREGROUND_BLUE);
    break;
  case YELLOW:    // Yellow on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);
    break;
  case MAGENTA:    // Magenta on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE);
    break;
  case CYAN:    // Cyan on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE);
    break;
  case GRAY:    // White on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
    break;
  case DARKRED:    // Red on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_RED);
    break;
  case DARKGREEN:    // Green on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_GREEN);
    break;
  case DARKBLUE:    // Blue on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_BLUE);
    break;
  case DARKYELLOW:    // Yellow on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_RED | FOREGROUND_GREEN);
    break;
  case DARKMAGENTA:    // Magenta on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_RED | FOREGROUND_BLUE);
    break;
  case DARKCYAN:    // Cyan on Black
    SetConsoleTextAttribute(hCon,
      FOREGROUND_GREEN | FOREGROUND_BLUE);
    break;
  }
  cur_color_ = next_color_;
}
#else
void GlobalLogger::ReallySetColor(){}
#endif 

void GlobalLogger::TestPrint()
{
  SetColor(WHITE);
  zout<<"WHITE ";
  SetColor(RED);
  zout<<"RED ";
  SetColor(GREEN);
  zout<<"GREEN ";
  SetColor(BLUE);
  zout<<"BLUE ";
  SetColor(YELLOW);
  zout<<"YELLOW ";
  SetColor(MAGENTA);
  zout<<"MAGENTA ";
  SetColor(CYAN);
  zout<<"CYAN ";
  SetColor(GRAY);
  zout<<"GRAY ";
  SetColor(DARKRED);
  zout << "DARKRED ";
  SetColor(DARKGREEN);
  zout << "DARKGREEN ";
  SetColor(DARKBLUE);
  zout << "DARKBLUE ";
  SetColor(DARKYELLOW);
  zout << "DARKYELLOW ";
  SetColor(DARKMAGENTA);
  zout << "DARKMAGENTA ";
  SetColor(DARKCYAN);
  zout << "DARKCYAN";
  zout << endl;

  VerboseLevel::Set(ZDEBUG);
  ZLOGD << "DEBUG MESSAGE\n";
  ZLOGV << "VERBOSE MESSAGE\n";
  ZLOGI << "INFO MESSAGE\n";
  ZLOGM << "MORE IMPORTANT MESSAGE\n";
  ZLOGE << "ERROR MESSAGE\n";
  ZLOGF << "FATAL MESSAGE\n";
  VerboseLevel::Restore();
}

GlobalLogger& operator<<(GlobalLogger &g,basic_ostream<char>& (&func)(basic_ostream<char>& _Ostr)) {
  g.ReallySetColor();
  for (vector<ostream*>::iterator vi = g.ostreams_.begin(); vi != g.ostreams_.end(); ++vi)
    (**vi) << func << flush;
  return g;
}

GlobalLogger& operator<<(GlobalLogger &g,ostream &v) {
  g.ReallySetColor();
  for (vector<ostream*>::iterator vi = g.ostreams_.begin(); vi != g.ostreams_.end(); ++vi)
    (**vi) << v << flush;
  return g;
}


GlobalLogger& operator<<(GlobalLogger &g, const string &v) {
  g.ReallySetColor();
  for (vector<ostream*>::iterator vi = g.ostreams_.begin(); vi != g.ostreams_.end(); ++vi)
    (**vi) << v << flush;
  return g;
}

GlobalLogger& operator<<(GlobalLogger &g, const char *v) {
  g.ReallySetColor();
  for (vector<ostream*>::iterator vi = g.ostreams_.begin(); vi != g.ostreams_.end(); ++vi)
    (**vi) << v << flush;
  return g;
}
////////////////////////////////////////////////
const LevelLogger& operator<<(const LevelLogger &g,basic_ostream<char>& (&func)(basic_ostream<char>& _Ostr)) {
  zout << func;
  return g;
}

const LevelLogger& operator<<(const LevelLogger &g,ostream &v) {
  zout << v;
  return g;
}


const LevelLogger& operator<<(const LevelLogger &g, const string &v) {
  zout << v;
  return g;
}

const LevelLogger& operator<<(const LevelLogger &g, const char *v) {
  zout << v;
  return g;
}

#ifdef ZZZ_OS_WIN
class StackWalkerToConsole : public StackWalker {
protected:
  virtual void OnOutput(LPCSTR szText) {
    ZLOGE << szText;
  }
};
#endif

LevelLogger::~LevelLogger() {
  if (level_ >= ZFATAL) {
    zout<<endl;
#ifdef ZZZ_DEBUG
    assert(false);
#else
  #ifdef ZZZ_OS_WIN
    StackWalkerToConsole sw;
    sw.ShowCallstack();
  #endif
    PressAnyKeyToContinue();
    exit(-1);
#endif
  }
}

string SplitLine3(const string &msg, int linelen) {
  int len=msg.size();
  if (len>=linelen) return string(msg);
  int flen=(linelen-len)/2;
  int blen=linelen-len-flen;
  return string(linelen, '=')+"\n*"+string(flen-1, ' ')+msg+string(blen-1, ' ')+"*\n"+string(linelen, '=');
}

}
